package ch.subgate.pongwars;

import android.content.Context;
import android.media.AudioManager;
import android.media.SoundPool;
import android.view.MotionEvent;

public abstract class GameContext {
    /** View surface width */
    public float surfaceWidth = 1.0f;

    /** View surface height */
    public float surfaceHeight = 1.0f;

    /** 4 by 3 arena width in pixels */
    public float battlefieldWidth;

    /** 4 by 3 arena height in pixels */
    public float battlefieldHeight;

    /** 4 by 3 arena top position */
    public float battlefieldTop;

    /** 4 by 3 arena left position */
    public float battlefieldLeft;

    /** 1 device independent arena unit in pixels */
    public float unitWidth;

    /** TimeOut is used to pause the game */
    public float timeOut;

    /** Message string */
    public String mMsg;

    /** Variable that tracks if a match is currently on or not */
    public boolean mIsOn;

    /*
	 * ---- Time measurement ----
	 */

    /** Time when the last frame was drawn */
    protected long mLastFrame;

    /** Time consumed between two iterations of the thread */
    protected float mDeltaTime;

    /*
     * ---- Game objects ----
	 */

    public Player playerRed;

    public Player playerBlue;

    public Ball ball;

    /*
     * ---- Sound playback ----
     */
    public SoundPool mSoundPool;

    public float volume;

    public int mLoaded = 0;

    public final int BOUNCE_RAQUET_1;
    public final int BOUNCE_RAQUET_2;
    public final int BOUNCE_RAQUET_SMASH;
    public final int BOUNCE_FLOOR;
    public final int APPLAUSE;

    public GameContext(Context context, float volume) {
        mIsOn = false;

        // Instantiate SoundPool and load sounds
        mSoundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 0);
        mSoundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
            @Override
            public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
                mLoaded++;
            } // onLoadComplete
        });
        APPLAUSE = mSoundPool.load(context, R.raw.applause, 1);
        BOUNCE_RAQUET_1 = mSoundPool.load(context, R.raw.bounce_raquet_1, 1);
        BOUNCE_RAQUET_2 = mSoundPool.load(context, R.raw.bounce_raquet_2, 1);
        BOUNCE_RAQUET_SMASH = mSoundPool.load(context, R.raw.bounce_raquet_smash, 1);
        BOUNCE_FLOOR = mSoundPool.load(context, R.raw.bounce_floor, 1);
        this.volume = volume;
    } // Constructor

    /**
     * Initialize GameContext object. Calling this method will not instantiate the associated GameObjects
     * but will simply initialize all the metric data for simulating and drawing the battlefield.
     * @param width The devices screen width in pixels
     * @param height The devices screen height in pixels
     */
    public void initMetrics(int width, int height) {
        surfaceWidth = (float)width;
        surfaceHeight = (float)height;

        float refWidth = surfaceWidth - Constants.BATTLEFIELD_MARGIN;
        float refHeight = surfaceHeight - Constants.BATTLEFIELD_MARGIN;

        if (refHeight * 0.75f > refWidth) {
            battlefieldWidth = refWidth;
            battlefieldHeight = refWidth / 3.0f * 4.0f;
        } else {
            battlefieldHeight = refHeight;
            battlefieldWidth = refHeight / 4.0f * 3.0f;
        }

        battlefieldTop = (surfaceHeight - battlefieldHeight) / 2.0f;
        battlefieldLeft = (surfaceWidth - battlefieldWidth) / 2.0f;

        unitWidth = battlefieldWidth / Constants.DIC_BATTLEFIELD_WIDTH;

        timeOut = Constants.TIME_TIMEOUT;

        mMsg = Constants.READY_TEXT;

    } // initMetrics

    /**
     * Initialize the game objects. Any instance of GameContext must call initMetrics and initGameObjects
     * first in order to be used.
     * @param paddleSize The paddle size used for this match
     * @param isServer If this is a network game, we need to know if this is the server or client side
     */
    public void initGameObjects(float paddleSize, boolean isServer) {
        // Instantiate game objects
        ball = new Ball(Constants.DIRECTION_BLUE_BEGINS);
        playerRed = new AIPlayer(true, paddleSize);
        playerBlue = new HumanPlayer(false, paddleSize);
        ((AIPlayer)playerRed).setBall(ball);
    } // initGameObjects

    /**
     * This method is called by the game loop thread. Its responsibility is to call the different
     * update() - methods of the simulated GameObjects.
     */
    public void update() {
        if (!mIsOn) { return; }

        // Get the time passed between the last cycle and this one
        long currentFrame = System.nanoTime();
        mDeltaTime = (currentFrame - mLastFrame) / 1000000000.0f;
        mLastFrame = currentFrame;

        if (timeOut > 0.0f) {
            timeOut -= mDeltaTime;
        } else {
            ball.update(mDeltaTime);
            playerBlue.update(mDeltaTime);
            playerRed.update(mDeltaTime);
            applyBounce();
            checkGoals();
            checkWin();
        } // if
    } // update

    public boolean getGameIsRunning() { return mIsOn; }

    public void release() { /* TODO */ };

    /**
     * Returns negative result if the red player has a match ball, positive if the blue player has
     * a match point and 0 if the game is currently drawn or the leading player has a score < 2
     * @return -1 if red has match ball, 1 for blue and 0 for no player having match ball
     */
    public int checkMatchBall() {
        if ((playerRed.mPoints > playerBlue.mPoints) && (playerRed.mPoints > 1)) { return -1; }
        else if ((playerBlue.mPoints > playerRed.mPoints) && (playerBlue.mPoints > 1)) { return 1; }
        else { return 0; }
    } // checkMatchBall

    public void handleInput(int action, float diX, float diY) {
        if (mIsOn) {
            boolean down = true;
            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
                down = false;
            } // if

            if (playerRed instanceof AIPlayer) {
                ((HumanPlayer)playerBlue).setX(diX);
                ((HumanPlayer)playerBlue).setIsCharging(!down);
            } else {
                ((HumanPlayer)playerRed).setX(diX);
                ((HumanPlayer)playerRed).setIsCharging(!down);
            } // if
        } else {
            if (diX > Constants.MESSAGE_LEFT
                    && diX < Constants.MESSAGE_RIGHT
                    && diY > Constants.MESSAGE_TOP
                    && diY < Constants.MESSAGE_BOTTOM) {
                mIsOn = true;
                // Initiate time measurement
                mLastFrame = System.nanoTime();
                mDeltaTime = 0.0f;
            } // if
        } // if
    } // handleInput

    protected void checkGoals() {
        if (ball.mY > Constants.DIC_BATTLEFIELD_HEIGHT) {
            // Ball is colliding with battlefield bottom
            playerRed.mPoints++;
            playerRed.reset();
            playerBlue.reset();
            ball.reset(Constants.DIRECTION_BLUE_BEGINS);
            timeOut = Constants.TIME_TIMEOUT;
            mMsg = Constants.READY_TEXT;
        } else if (ball.mY < 0.0f) {
            // Ball is colliding with battlefield top
            playerBlue.mPoints++;
            playerRed.reset();
            playerBlue.reset();
            ball.reset(Constants.DIRECTION_RED_BEGINS);
            timeOut = Constants.TIME_TIMEOUT;
            mMsg = Constants.READY_TEXT;
        } // if
    } // checkGoals

    protected void checkWin() {
        if (
                ((playerRed.mPoints >= 3) && (playerRed.mPoints - playerBlue.mPoints >= 2))
                || ((playerBlue.mPoints >= 3) && (playerBlue.mPoints - playerRed.mPoints >= 2))) {
            timeOut = Constants.TIME_TIMEOUT;
            mIsOn = false;
            if (playerRed.mPoints > playerBlue.mPoints) { // red player won
                ball.reset(Constants.DIRECTION_BLUE_BEGINS);
                mMsg = Constants.RED_WIN_TEXT;
            } else { // blue player won
                ball.reset(Constants.DIRECTION_RED_BEGINS);
                mMsg = Constants.BLUE_WIN_TEXT;
            } // if
            playerRed.mPoints = 0;
            playerBlue.mPoints = 0;
            mSoundPool.play(APPLAUSE, volume, volume, 0, 0, 1.0f);
        } // if
    } // checkWin

    protected void applyBounce() {
        // Battlefield bounce
        if (ball.mX > Constants.DIC_BATTLEFIELD_WIDTH - 0.5f) {
            // Ball is colliding with battlefield right
            ball.mX = Constants.DIC_BATTLEFIELD_WIDTH - 0.5f;
            ball.direction = GameHelper.mirrorDirectionXAxis(ball.direction);
            mSoundPool.play(BOUNCE_FLOOR, volume, volume, 0, 0, 1.0f);
        } else if (ball.mX < 0.5f) {
            // Ball is colliding with battlefield left
            ball.mX = 0.5f;
            ball.direction = GameHelper.mirrorDirectionXAxis(ball.direction);
            mSoundPool.play(BOUNCE_FLOOR, volume, volume, 0, 0, 1.0f);
        } // if

        // Player bounce
        if (ball.mY < playerRed.mY + 1.0f && ball.mY > playerRed.mY - 1.0f) {
            if (
                    ball.mX > playerRed.mX - playerRed.mSize - 0.5f
                            && ball.mX < playerRed.mX + playerRed.mSize + 0.5f) {
                if (ball.bounce(playerRed)) {
                    mSoundPool.play(BOUNCE_RAQUET_SMASH, volume, volume, 0, 0, 1.0f);
                } else {
                    mSoundPool.play(BOUNCE_RAQUET_1, volume, volume, 0, 0, 1.0f);
                } // if
            }
        } else  if (ball.mY > playerBlue.mY - 1.0f && ball.mY < playerBlue.mY + 1.0f) {
            if (
                    ball.mX > playerBlue.mX - playerBlue.mSize - 0.5f
                            && ball.mX < playerBlue.mX + playerBlue.mSize + 0.5f) {
                if (ball.bounce(playerBlue)) {
                    mSoundPool.play(BOUNCE_RAQUET_SMASH, volume, volume, 0, 0, 1.0f);
                } else {
                    mSoundPool.play(BOUNCE_RAQUET_2, volume, volume, 0, 0, 1.0f);
                } // if
            } // if
        } // if
    } // applyBounce

} // GameContext
